Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

✨ Discord notification provider #280

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ camply campgrounds --search "Fire Lookout Towers" --state CA
Search for available campsites, get a notification whenever one becomes
available, and continue searching after the first one is found. The below command
is using `silent` notifications as an example but camply also supports `Email`,
`Slack`, `Twilio` (SMS), `Pushover`, `Pushbullet`, `Ntfy`, `Apprise`, and `Telegram`.
`Slack`, `Discord`, `Twilio` (SMS), `Pushover`, `Pushbullet`, `Ntfy`, `Apprise`, and `Telegram`.

```commandline
camply campsites \
Expand Down
2 changes: 2 additions & 0 deletions camply/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .file_config import FileConfig
from .notification_config import (
AppriseConfig,
DiscordConfig,
EmailConfig,
NtfyConfig,
PushbulletConfig,
Expand All @@ -31,6 +32,7 @@
"PushbulletConfig",
"PushoverConfig",
"SlackConfig",
"DiscordConfig",
"TelegramConfig",
"TwilioConfig",
"SearchConfig",
Expand Down
1 change: 1 addition & 0 deletions camply/config/file_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class FileConfig:
"notes": "Enables Pushbullet Notifications",
},
SLACK_WEBHOOK={"default": "", "notes": "Enables Slack Notifications"},
DISCORD_WEBHOOK={"default": "", "notes": "Enables Discord Notifications"},
TELEGRAM_BOT_TOKEN={"default": "", "notes": "Enables Telegram Notifications"},
TELEGRAM_CHAT_ID={
"default": "",
Expand Down
7 changes: 7 additions & 0 deletions camply/config/notification_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ class SlackConfig:

SLACK_WEBHOOK: Optional[str] = getenv("SLACK_WEBHOOK", None)

class DiscordConfig:
"""
Discord Notification Config Class
"""

DISCORD_WEBHOOK: Optional[str] = getenv("DISCORD_WEBHOOK", None)


class TelegramConfig:
"""
Expand Down
2 changes: 2 additions & 0 deletions camply/notifications/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from .apprise import AppriseNotifications
from .discord import DiscordNotifications
from .email_notifications import EmailNotifications
from .multi_provider_notifications import CAMPSITE_NOTIFICATIONS, MultiNotifierProvider
from .pushbullet import PushbulletNotifications
Expand All @@ -17,6 +18,7 @@
"PushbulletNotifications",
"PushoverNotifications",
"TelegramNotifications",
"DiscordNotifications",
"TwilioNotifications",
"EmailNotifications",
"SilentNotifications",
Expand Down
113 changes: 113 additions & 0 deletions camply/notifications/discord.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""
Push Notifications via Discord
"""

import logging
from typing import List

import requests

from camply.config import DiscordConfig
from camply.containers import AvailableCampsite
from camply.notifications.base_notifications import BaseNotifications

logger = logging.getLogger(__name__)


class DiscordNotifications(BaseNotifications):
"""
Push Notifications via Discord
"""

def __init__(self):
super().__init__()
self.session.headers.update({"Content-Type": "application/json"})
if any([DiscordConfig.DISCORD_WEBHOOK is None, DiscordConfig.DISCORD_WEBHOOK == ""]):
Copy link
Owner

@juftin juftin Aug 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple lines like this that haven't been formatted by black / ruff - that's why we're seeing linting errors.

pre-commit resolves this with hatch - so either of these commands will format the codebase for you:

hatch run format
pre-commit run --all-files


This part is kind of confusing. CI/CD runs both linting (black/ruff) and type checking (mypy) on the codebase inside the Lint job - but only linting errors actually cause it to fail. type-checking issues (on related and unrelated code) will still show up as annotations on the PR but won't actually cause the Lint job to fail.

image

warning_message = (
"Discord is not configured properly. To send Discord messages "
"make sure to run `camply configure` or set the "
"proper environment variable: `DISCORD_WEBHOOK`."
)
logger.error(warning_message)
raise EnvironmentError(warning_message)

def send_message(self, message: str, **kwargs) -> requests.Response:
"""
Send a message via Discord - if environment variables are configured.

Parameters
----------
message: str

Returns
-------
requests.Response
"""
message_json = kwargs
if message:
message_json["content"] = message

logger.debug(message_json)
response = self.session.post(
url=DiscordConfig.DISCORD_WEBHOOK,
json=message_json,
)
try:
response.raise_for_status()
except requests.HTTPError as he:
logger.warning(
"Notifications weren't able to be sent to Discord. "
"Your configuration might be incorrect."
)
raise ConnectionError(response.text) from he
return response

def block_for_campsite(self, campsite: AvailableCampsite):
message_title, formatted_dict = self.format_standard_campsites(
campsite=campsite,
)

# Remove items that will be templated as part of the embed.
del formatted_dict["Recreation Area"]
del formatted_dict["Booking Date"]
del formatted_dict["Booking End Date"]
del formatted_dict["Facility Name"]
del formatted_dict["Booking Link"]
del formatted_dict["Campsite Site Name"]
del formatted_dict["Campsite Loop Name"]
del formatted_dict["Recreation Area Id"]
del formatted_dict["Facility Id"]
del formatted_dict["Campsite Id"]

return {
"author": {
"name": f"🏕 {campsite.recreation_area}"
},
"title": f"{campsite.facility_name} {campsite.campsite_loop_name} #{campsite.campsite_site_name}",
"description": f"{campsite.booking_date:%Y/%m/%d} to {campsite.booking_end_date:%Y/%m/%d}",
"url": campsite.booking_url,
"color": 2375436,
"fields": [
{
"name": key,
"value": str(value)
} for key, value in formatted_dict.items()
],
"footer": {
"text": "camply, the campsite finder ⛺️"
}
}
Comment on lines +70 to +99
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple notes on this - I think there are a good number of fields that we want to keep from our formatted_dict here - although it can be quite verbose when looking at the notifications the extra context can be useful.

  • This title field here assumes that the campsite has a numeric campsite_loop_name - but in a good number of campsites this is a string value.
  • There are provider's who don't allow the passing of campsite ID via the booking URL - for those cases we'll need to keep the campsite ID in the message
  • Is it possible to have a notification open in a "collapsed" view that can be expanded? This would be nice to have the default view be simple, but with expanded details.

See here for the test-notifications CLI command:

camply test-notifications --notifications discord
image

Here's the same notification, that instead uses message_title and doesn't remove all the fields - I think some of these can be omitted though like Booking Date / Booking URL which are provided elsewhere
image


def send_campsites(self, campsites: List[AvailableCampsite], **kwargs):
"""
Send a message with a campsite object

Parameters
----------
campsites: AvailableCampsite
"""
if campsites:
self.send_message(
message="",
embeds=[self.block_for_campsite(campsite) for campsite in campsites],
)
2 changes: 2 additions & 0 deletions camply/notifications/multi_provider_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Dict, List, Type, Union

from camply.containers import AvailableCampsite
from camply.notifications import DiscordNotifications
from camply.notifications.apprise import AppriseNotifications
from camply.notifications.base_notifications import BaseNotifications, NotificationError
from camply.notifications.email_notifications import EmailNotifications
Expand All @@ -27,6 +28,7 @@
"apprise": AppriseNotifications,
"pushbullet": PushbulletNotifications,
"slack": SlackNotifications,
"discord": DiscordNotifications,
"telegram": TelegramNotifications,
"twilio": TwilioNotifications,
"silent": SilentNotifications,
Expand Down
3 changes: 2 additions & 1 deletion docs/command_line_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ and a link to make the booking. Required parameters include `--start-date`, `--e
[\*\*_example_](#continue-looking-after-the-first-match-is-found)
- `--notifications`: `NOTIFICATIONS`
- Enables continuous searching. Types of notifications to receive. Options available
are `pushover`, `email`, `ntfy`, `apprise`, `pushbullet`, `slack`, `telegram`, `twilio`, `silent`.
are `pushover`, `email`, `ntfy`, `apprise`, `pushbullet`, `slack`, `discord`, `telegram`, `twilio`, `silent`.
Defaults to `silent` - which just logs messages to console.
[\*\*_example_](#send-a-push-notification)
- `--equipment`
Expand Down Expand Up @@ -457,6 +457,7 @@ camply supports notifications via a number of services:
- [Apprise](https://github.com/caronc/apprise)
- [Pushbullet](https://www.pushbullet.com/#settings/account)
- [Slack](https://slack.com)
- [Discord](https://discord.com)
- [Telegram](https://core.telegram.org/bots)
- [Twilio (SMS)](https://www.twilio.com)
- Silent
Expand Down
3 changes: 3 additions & 0 deletions docs/examples/example.camply
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ PUSHBULLET_API_TOKEN=""
# REQUIRED TO SEND SLACK NOTIFICATIONS
SLACK_WEBHOOK=""

# REQUIRED TO SEND DISCORD NOTIFICATIONS
DISCORD_WEBHOOK=""

# REQUIRED TO SEND TELEGRAM NOTIFICATIONS
TELEGRAM_BOT_TOKEN=""
TELEGRAM_CHAT_ID=""
Expand Down
2 changes: 2 additions & 0 deletions docs/how_to_run.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ available.
- `TWILIO_DEST_NUMBERS`
- Slack Notifications
- `SLACK_WEBHOOK`
- Discord Notifications
- `DISCORD_WEBHOOK`
- Telegram Notifications
- `TELEGRAM_BOT_TOKEN`
- `TELEGRAM_CHAT_ID`
Expand Down