Skip to content

Commit

Permalink
v1.0.0 Release
Browse files Browse the repository at this point in the history
  • Loading branch information
finlaysawyer committed Jan 10, 2021
2 parents 9a4a83a + 0c367f1 commit c9f73be
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 128 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/linting.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Lint

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
lint:
name: Run linters
runs-on: ubuntu-latest

steps:
- name: Check out Git repository
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.7

- name: Install Python dependencies
run: pip install black flake8

- name: Run linters
uses: wearerequired/lint-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
black: true
flake8: true
auto_fix: true
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Changelog

## [1.0.0] - 2021-01-10

### Added
- **Added support for tracking website uptime in montoring.**
- **Added http command for manual checking of website uptime.**
- Added more type hints and data types for function parameters
- Linting workflow for PRs and master releases
- More annotations for commands
- Added logging

### Changed
- **No longer need to reload the bot to update the config files.**
- Status command moved into the monitor cog.
- Status command and up/down notifications now show monitor type.
- Discord.py upgraded to 1.6 and aiohttp bumped to 3.7.3
- Main file renamed to `bot.py`

[1.0.0]: https://github.com/finlaysawyer/discord-uptime/compare/v0.0.1...v1.0.0
36 changes: 27 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# discord-uptime
Discord bot to monitor uptime and ping addresses
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

Built using discord.py 1.5.x and ping3 libraries
Discord Uptime is a Discord bot that allows you to monitor the uptime of services using ICMP ping and http requests.
There are also commands avaliable to make manual requests. Built using discord.py 1.6.x, ping3 and aiohttp

## Installation
**Requires Python 3.6+**
Expand All @@ -12,26 +13,43 @@ Install dependencies:

Bot setup (Rename config.example.json and edit the default values):
* `token` - Discord bot token
* `prefix` - Discord bot prefix
* `notification_channel` - Channel ID where down/up notifications will be sent
* `role_to_mention` - Role ID which will be tagged in down/up notifications
* `secs_between_ping` - How many seconds between each uptime check
* `http_timeout` - How many seconds before a HTTP request should timeout

No privileged intents are currently needed to run the bot.

Servers setup:

To add more servers to ping, simply add a new object and specify the `name` and `address` in servers.json
## Servers Configuration
Servers should be setup similar to the examples already in `server.json`:
* There are two supported types: `http` and `ping`
```json
[
{
"name": "Google",
"type": "http",
"address": "google.com"
},
{
"name": "Cloudflare",
"type": "ping",
"address": "1.1.1.1"
}
]
```

## Commands
> Default Prefix: >
* `ping <address> [pings]` - Pings an address once, or for the amount specified via pings
* `status` - Displays the status of all servers
* `ping <address> [pings]` - Pings an address once, or for the amount specified via pings and returns the delay in ms
* `http <address>` - Performs a HTTP request to the specified address and returns the response code
* `status` - Displays the status of all servers setup in `servers.json`

## Screenshots
> Status Command
![status](https://i.gyazo.com/aafabf21cadfa133caa974dad1a489d4.png)
![status](https://i.gyazo.com/6d5e0c4fbdb5ff52619d86eef827e369.png)
> Uptime & Downtime Notifications
![uptime](https://i.gyazo.com/e81570754dfdb59f6f648946a504877f.png)
![uptime](https://i.gyazo.com/803aebfcb3833ac8de7bd38e18378a29.png)
43 changes: 43 additions & 0 deletions bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import logging
import os
import sys

from discord import Intents
from discord.ext import commands

from utils.config import get_config

bot = commands.Bot(command_prefix=get_config("prefix"))
logging.basicConfig(
format="%(levelname)s | %(asctime)s | %(name)s | %(message)s",
stream=sys.stdout,
level=logging.INFO,
)


class DiscordUptime(commands.Bot):
def __init__(self):
super().__init__(
command_prefix=get_config("prefix"),
description="Bot to monitor uptime of services",
reconnect=True,
intents=Intents.default(),
)
self.bot = bot

async def on_ready(self):
print(f"Logged in as {self.user}")

for filename in os.listdir("./cogs"):
if filename.endswith(".py"):
self.load_extension(f"cogs.{filename[:-3]}")

async def on_command_error(self, ctx, error):
if isinstance(error, (commands.BadArgument, commands.MissingRequiredArgument)):
return await ctx.send(error)
else:
return


if __name__ == "__main__":
DiscordUptime().run(get_config("token"))
145 changes: 104 additions & 41 deletions cogs/monitor.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,127 @@
import asyncio
from datetime import timedelta

import aiohttp
import discord
from discord.ext import tasks, commands
from ping3 import ping

from utils import config as cfg

currently_down = {}


async def notify_down(name, address, channel, reason):
if address not in currently_down:
currently_down.update({address: 0})
embed = discord.Embed(
title=f"**:red_circle: {name} is down!**",
color=16711680
)
embed.add_field(name="Address", value=address, inline=False)
embed.add_field(name="Reason", value=reason, inline=False)
await channel.send(embed=embed)
await channel.send(f"<@&{cfg.config['role_to_mention']}>", delete_after=3)
else:
currently_down[address] = currently_down.get(address, 0) + cfg.config['secs_between_ping']


async def notify_up(name, address, channel):
if address in currently_down:
embed = discord.Embed(
title=f"**:green_circle: {name} is up!**",
color=65287
)
embed.add_field(name="Address", value=address, inline=False)
embed.add_field(name="Downtime", value=str(timedelta(seconds=currently_down[address])), inline=False)
await channel.send(embed=embed)
await channel.send(f"<@&{cfg.config['role_to_mention']}>", delete_after=3)
currently_down.pop(address)
from utils.config import get_config, get_servers


class Monitor(commands.Cog):

def __init__(self, bot):
self.bot = bot
self.currently_down = {}
self.monitor_uptime.start()

def cog_unload(self):
self.monitor_uptime.cancel()

@tasks.loop(seconds=cfg.config['secs_between_ping'])
async def monitor_uptime(self):
async def notify_down(
self, server: object, channel: discord.TextChannel, reason: str
) -> None:
"""
Sends an embed to indicate a service is offline
:param server: Server object to extract data from
:param channel: Channel to send the notification to
:param reason: Reason why the service is down
"""
if server["address"] not in self.currently_down:
self.currently_down.update({server["address"]: 0})
embed = discord.Embed(
title=f"**:red_circle: {server['address']} is down!**", color=16711680
)
embed.add_field(name="Address", value=server["address"], inline=False)
embed.add_field(name="Type", value=server["type"], inline=False)
embed.add_field(name="Reason", value=reason, inline=False)
await channel.send(embed=embed)
await channel.send(f"<@&{get_config('role_to_mention')}>", delete_after=3)
else:
self.currently_down[server["address"]] = self.currently_down.get(
server["address"], 0
) + get_config("secs_between_ping")

async def notify_up(self, server: object, channel: discord.TextChannel) -> None:
"""
Sends an embed to indicate a service is online
:param server: Server object to extract data from
:param channel: Channel to send the notification to
"""
if server["address"] in self.currently_down:
embed = discord.Embed(
title=f"**:green_circle: {server['name']} is up!**", color=65287
)
embed.add_field(name="Address", value=server["address"], inline=False)
embed.add_field(name="Type", value=server["type"], inline=False)
embed.add_field(
name="Downtime",
value=str(timedelta(seconds=self.currently_down[server["address"]])),
inline=False,
)
await channel.send(embed=embed)
await channel.send(f"<@&{get_config('role_to_mention')}>", delete_after=3)
self.currently_down.pop(server["address"])

@tasks.loop(seconds=get_config("secs_between_ping"))
async def monitor_uptime(self) -> None:
"""Checks the status of each server and sends up/down notifications"""
await self.bot.wait_until_ready()

channel = self.bot.get_channel(cfg.config['notification_channel'])
channel = self.bot.get_channel(get_config("notification_channel"))

for i in cfg.servers:
if ping(i["address"]) is False:
await notify_down(i['name'], i["address"], channel, "Host unknown")
elif ping(i["address"]) is None:
await notify_down(i['name'], i["address"], channel, "Timed out")
for i in get_servers():
if i["type"] == "ping":
if ping(i["address"]) is False:
await self.notify_down(i, channel, "Host unknown")
elif ping(i["address"]) is None:
await self.notify_down(i, channel, "Timed out")
else:
await self.notify_up(i, channel)
else:
await notify_up(i['name'], i["address"], channel)
address = i["address"]
timeout = get_config("http_timeout")

if not address.startswith("http"):
address = f"http://{address}"

async with aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=timeout)
) as session:
try:
async with session.get(address) as res:
if res.ok:
await self.notify_up(i, channel)
else:
await self.notify_down(i, channel, res.reason)
except asyncio.TimeoutError:
await self.notify_down(i, channel, "Timed out")
except aiohttp.ClientError:
await self.notify_down(i, channel, "Connection failed")

@commands.command(brief="Checks status of servers being monitored", usage="status")
async def status(self, ctx) -> None:
"""Returns an embed showing the status of each monitored server"""
embed = discord.Embed(
title="**Monitor Status**", color=16711680 if self.currently_down else 65287
)

for i in get_servers():
if i["address"] in self.currently_down:
downtime = str(timedelta(seconds=self.currently_down[i["address"]]))
embed.add_field(
name=f"{i['name']} ({i['type']})",
value=f":red_circle: {i['address']} ({downtime})",
inline=False,
)
else:
embed.add_field(
name=f"{i['name']} ({i['type']})",
value=f":green_circle: {i['address']}",
inline=False,
)

await ctx.send(embed=embed)


def setup(bot):
Expand Down
Loading

0 comments on commit c9f73be

Please sign in to comment.