Skip to content
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
data/
__pycache__/
.env
.env
.idea**
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY ip_notify.py .
RUN mkdir /app/services
ADD services ./services/

# Copy entrypoint
COPY --chmod=744 entrypoint.sh .
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# IP Notify

## Sends a Discord webhook notification when your public IP changes
## Sends a webhook notification when your public IP changes

Explanation: I run a number of services behind a NAT at home which are only
accessible through a WireGuard VPN. My IP rarely if ever changes, but it it does,
accessible through a WireGuard VPN. My IP rarely if ever changes, but if it does,
this script will send me a Discord notification so I can update my VPN endpoint
while away from home.

Expand All @@ -23,14 +23,17 @@ and checks for differences on next run
- Or run the supplied docker image and compose project (cron in docker)

## Args
- `--webhook` The Discord webhook endpoint (Required)
- `--service` The desired service to send the notification to. Currently, `discord` and `msteams` are possible options.
- `--webhook` The webhook endpoint (Required)
- `-o | --cache-dir` The file to write save the previous ip to (Default: `$XDG_CONFIG_HOME/ip-notify/old_ip`)
- `--test` Send the webhook even if the IP hasn't changed

## Env Vars
Most useful when running in Docker (See `docker-compose.yml`).
```
# The discord webhook url. Include here or in .env file
# The desired webhook service to use.
WEBHOOK_SERVICE=discord
# The webhook url. Include here or in .env file
WEBHOOK_URL=${WEBHOOK_URL}
# The color of the Discord Embed in hex
EMBED_COLOR=1bb106
Expand Down
4 changes: 3 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ services:
- ./data:/data
# user: '1000'
environment:
# The discord webhook url. Include here or in .env file
# The webhook url. Include here or in .env file
- WEBHOOK_URL=${WEBHOOK_URL}
# The service used to post webhooks to
- WEBHOOK_SERVICE=discord
# The color of the Discord Embed in hex
- EMBED_COLOR=1bb106
# The link when clicking the Embed author
Expand Down
46 changes: 19 additions & 27 deletions ip_notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import os
import logging
from logging.handlers import RotatingFileHandler
from discord_webhook import DiscordWebhook, DiscordEmbed
import argparse
from argparse import Namespace
import services.discord as discord
import services.msteams as msteams

IP_PROVIDERS = [
# "http://ifconfig.me",
Expand All @@ -18,10 +19,15 @@

def get_args() -> Namespace:
args = argparse.ArgumentParser()
args.add_argument(
"--service",
type=str,
help="Type of service to send webhook to; e.g. discord or msteams",
)
args.add_argument(
"--webhook",
type=str,
help="URL of Discord webhook endpoint",
help="URL of webhook endpoint",
)
args.add_argument(
"-o",
Expand All @@ -41,6 +47,7 @@ def get_config() -> Namespace:
args = get_args()
config = Namespace()
config.test = args.test
config.service = args.service or os.getenv("WEBHOOK_SERVICE")
config.webhook = args.webhook or os.getenv("WEBHOOK_URL")
config.embed_color = os.getenv("EMBED_COLOR", "1bb106")
config.author_url = os.getenv("AUTHOR_URL", "https://github.com/jack-mil/ip-notify")
Expand Down Expand Up @@ -82,30 +89,16 @@ def setup_logging():


def send_notification(webhook_url, current_ip, old_ip, config):
webhook = DiscordWebhook(
url=webhook_url, username="IP Notify", avatar_url=config.author_url
)
# Create and format the embed
embed = DiscordEmbed(title="IP Address Changed", color=config.embed_color)
embed.set_author(
name="IP Notify",
url=config.author_url,
icon_url=config.icon_url,
)

# Add embed fields with Discord formatting
embed.add_embed_field(name="New :green_circle:", value=f"**{current_ip}**")
embed.add_embed_field(name="Old :red_circle:", value=f"~~{old_ip}~~")
# Set footer timestamp to now
embed.set_footer(text="Occured")
embed.set_timestamp()

# Add the embed
webhook.add_embed(embed)

# Send the webhook notification
response = webhook.execute()
logging.info("Sent discord notification")
if config.service.lower() == 'discord':
if discord.DiscordWebhookService.send_notification(webhook_url, current_ip, old_ip, config):
logging.info("Sent Discord notification")
else:
logging.error('Failed to send Discord notification')
elif config.service.lower() == 'msteams':
if msteams.TeamsWebhookService.send_notification(webhook_url, current_ip, old_ip, config):
logging.info('Sent Teams notification')
else:
logging.error('Failed to send Teams notification')


def get_current_ip(providers: list[str]):
Expand Down Expand Up @@ -145,7 +138,6 @@ def save_current_ip(ip: str, ip_file: str):
except OSError as e:
logger.error(f"OSERROR: Could not save current IP: {e}")


if __name__ == "__main__":
"""Run once to check current IP against old IP and notify if changed"""

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
requests
requests~=2.31.0
discord-webhook
3 changes: 3 additions & 0 deletions services/ServiceInterface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class WebhookServiceInterface:
def send_notification(self, url, current_ip, old_ip, config) -> bool:
pass
33 changes: 33 additions & 0 deletions services/discord.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from discord_webhook import DiscordWebhook, DiscordEmbed
from services.ServiceInterface import WebhookServiceInterface


class DiscordWebhookService(WebhookServiceInterface):
@staticmethod
def send_notification(url, current_ip, old_ip, config) -> bool:
webhook = DiscordWebhook(
url=url, username="IP Notify", avatar_url=config.author_url
)
# Create and format the embed
embed = DiscordEmbed(title="IP Address Changed", color=config.embed_color)
embed.set_author(
name="IP Notify",
url=config.author_url,
icon_url=config.icon_url,
)

# Add embed fields with Discord formatting
embed.add_embed_field(name="New :green_circle:", value=f"**{current_ip}**")
embed.add_embed_field(name="Old :red_circle:", value=f"~~{old_ip}~~")
# Set footer timestamp to now
embed.set_footer(text="Occured")
embed.set_timestamp()

# Add the embed
webhook.add_embed(embed)

# Send the webhook notification
response = webhook.execute()
return response.ok
# No idea if pass is still needed here... Not a python expert
pass
52 changes: 52 additions & 0 deletions services/msteams.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import requests
from services.ServiceInterface import WebhookServiceInterface


# Inspiration from https://stackoverflow.com/questions/59371631/send-automated-messages-to-microsoft-teams-using-python

class TeamsWebhookService(WebhookServiceInterface):
@staticmethod
def send_notification(url, current_ip, old_ip, config) -> bool:
json_data = TeamsWebhookService.get_card_data(current_ip, old_ip)
result = requests.post(url, json=json_data)
return result.ok
# No idea if pass is still needed here... Not a python expert
pass

@staticmethod
def get_card_data(current_ip, old_ip):
return {
"type": "message",
"attachments": [
{
"contentType":"application/vnd.microsoft.card.adaptive",
"contentUrl": "null",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.6",
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "IP change detected"
},
{
"type": "FactSet",
"facts": [
{
"title": "Old:",
"value": old_ip
},
{
"title": "New:",
"value": current_ip
}
]
}
]
}
}
]
}