Skip to content
This repository was archived by the owner on Dec 13, 2025. It is now read-only.

Commit 3c8d93d

Browse files
committed
Clean up code. Essential messaging function left.
1 parent 283c112 commit 3c8d93d

File tree

5 files changed

+13
-805
lines changed

5 files changed

+13
-805
lines changed

README.rst

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,12 @@ It registers the following commands:
3535
* ``dbot-run`` - main CLI entry-point
3636
* ``dbot-message`` - (short-hand) to send a message, or even pipe `-` message contents
3737
* ``dbot-file`` - (short-hand) to send a file with an message
38-
* ``dbot-info`` - (short-hand) to send a message with system information
39-
(*extra dependencies have to be installed!*)
40-
* ``dbot-observe`` - a blocking script, that runs periodic system checks and notifies about shortages
41-
(*requires extra dependencies to be installed*)
4238

4339
Requirements
4440
------------
4541

4642
* Python >= 3.6 (*see badges above*)
4743
* `discord.py <https://github.com/Rapptz/discord.py>`_
48-
* Extra:
49-
50-
* ``cpu``: `psutil <https://github.com/giampaolo/psutil>`_
51-
* ``gpu``: `GPUtil <https://github.com/anderskm/gputil>`_
5244

5345
Installation
5446
------------
@@ -59,13 +51,6 @@ Installation
5951
6052
Optionally, install it locally with ``--user``.
6153

62-
For system info messages using ``dbot-info`` or ``dbot-run info [...]``, you have to install extra dependencies.
63-
You can choose between cpu (cpu + disk information) and gpu (``nvidia-smi`` information):
64-
65-
.. code-block:: bash
66-
67-
python3 -m pip install discord-notifier-bot[cpu,gpu]
68-
6954
Configuration
7055
-------------
7156

@@ -149,36 +134,6 @@ You may also run the bot with the python module notation. But it will only run t
149134
150135
python -m discord_notifier_bot [...]
151136
152-
System Observer Bot
153-
~~~~~~~~~~~~~~~~~~~
154-
155-
As of version **0.2.***, I have included some basic system observation code.
156-
Besides the ``dbot-info`` command that sends a summary about system information to a Discord channel,
157-
an *observation service* with ``dbot-observe`` is included.
158-
The command runs a looping Discord task that checks every **5 min** some predefined system conditions,
159-
and sends a notification if a ``badness`` value is over a threshold.
160-
This ``badness`` value serves to either immediatly notify a channel if a system resource is exhausted or after some repeated limit exceedances.
161-
162-
The code (checks and limits) can be found in `discord_notifier_bot.sysinfo <https://github.com/Querela/discord-notifier-bot/blob/master/discord_notifier_bot/sysinfo.py>`_.
163-
The current limits are some less-than educated guesses, and are subject to change.
164-
Dynamic configuration is currently not an main issue, so users may need to clone the repo, change values and install the python package from source:
165-
166-
.. code-block:: bash
167-
168-
git clone https://github.com/Querela/discord-notifier-bot.git
169-
cd discord-notifier-bot/
170-
# [do the modifications in discord_notifier_bot/sysinfo.py]
171-
python3 -m pip install --user --upgrade --editable .[cpu,gpu]
172-
173-
The system information gathering requires the extra dependencies to be installed, at least ``cpu``, optionally ``gpu``.
174-
175-
I suggest that you provide a different Discord channel for those notifications and create an extra ``.dbot-observer.conf`` configuration file that can then be used like this:
176-
177-
.. code-block:: bash
178-
179-
dbot-observe [-d] -c ~/.dbot-observer.conf
180-
181-
182137
Embedded in other scripts
183138
~~~~~~~~~~~~~~~~~~~~~~~~~
184139

discord_notifier_bot/bot.py

Lines changed: 4 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
1-
import datetime
21
import logging
32
import os
4-
from collections import defaultdict
53

64
import discord
7-
from discord.ext import commands, tasks
85

9-
from discord_notifier_bot.sysinfo import get_info_message
10-
from discord_notifier_bot.sysinfo import get_local_machine_name
11-
from discord_notifier_bot.sysinfo import (
12-
get_cpu_info,
13-
get_disk_info,
14-
get_gpu_info,
15-
)
16-
from discord_notifier_bot.sysinfo import make_observable_limits
17-
from discord_notifier_bot.sysinfo import NotifyBadCounterManager
186

197
LOGGER = logging.getLogger(__name__)
208

9+
2110
# ---------------------------------------------------------------------------
2211

2312

@@ -49,10 +38,12 @@ async def do_work(self):
4938

5039

5140
class SendSingleFileMessageClient(AbstractSingleActionClient):
52-
def __init__(self, channel_id, file2send, message=None, *args, **kwargs):
41+
def __init__(self, channel_id, file2send, *args, message=None, **kwargs):
5342
super().__init__(*args, **kwargs)
5443
self.channel_id = channel_id
5544
self.file2send = file2send
45+
if message is None:
46+
message = ""
5647
self.message = message
5748

5849
async def do_work(self):
@@ -82,205 +73,3 @@ def send_file(token, channel_id, message, filename):
8273

8374

8475
# ---------------------------------------------------------------------------
85-
86-
87-
def make_sysinfo_embed():
88-
embed = discord.Embed(title=f"System Status of `{get_local_machine_name()}`")
89-
# embed.set_thumbnail(url="") # TODO: add "private" logo (maybe as an config option ...)
90-
embed.add_field(
91-
name="System information", value=get_cpu_info() or "N/A", inline=False
92-
)
93-
embed.add_field(
94-
name="Disk information", value=get_disk_info() or "N/A", inline=False
95-
)
96-
embed.add_field(name="GPU information", value=get_gpu_info() or "N/A", inline=False)
97-
embed.set_footer(text=f"Date: {datetime.datetime.now()}")
98-
99-
return embed
100-
101-
102-
# ---------------------------------------------------------------------------
103-
104-
105-
class SystemResourceObserverCog(commands.Cog, name="System Resource Observer"):
106-
def __init__(self, bot, channel_id):
107-
self.bot = bot
108-
self.channel_id = channel_id
109-
self.local_machine_name = get_local_machine_name()
110-
111-
self.limits = dict()
112-
self.bad_checker = NotifyBadCounterManager()
113-
self.stats = defaultdict(int)
114-
115-
self.init_limits()
116-
117-
def init_limits(self):
118-
# TODO: pack them in an optional file (like Flask configs) and try to load else nothing.
119-
self.limits.update(make_observable_limits())
120-
121-
def reset_notifications(self):
122-
self.bad_checker.reset()
123-
124-
@tasks.loop(minutes=5.0)
125-
async def observe_system(self):
126-
LOGGER.debug("Running observe system task loop ...")
127-
128-
async with self.bot.get_channel(self.channel_id).typing():
129-
# perform checks
130-
for name, limit in self.limits.items():
131-
try:
132-
await self.run_single_check(name, limit)
133-
except Exception as ex:
134-
LOGGER.debug(
135-
f"Failed to evaulate check: {limit.name}, reason: {ex}"
136-
)
137-
138-
self.stats["num_checks"] += 1
139-
140-
async def run_single_check(self, name, limit):
141-
LOGGER.debug(f"Running check: {limit.name}")
142-
143-
cur_value = limit.fn_retrieve()
144-
ok = limit.fn_check(cur_value, limit.threshold)
145-
146-
if not ok:
147-
# check of limit was "bad", now check if we have to notify someone
148-
self.stats["num_limits_reached"] += 1
149-
self.stats[f"num_limits_reached:{name}:{limit.name}"] += 1
150-
151-
# increase badness
152-
self.bad_checker.increase_counter(name, limit)
153-
if self.bad_checker.should_notify(name, limit):
154-
# check if already notified (that limit reached)
155-
# even if shortly recovered but not completely, e. g. 3->2->3 >= 3 (thres) <= 0 (not completely reset)
156-
await self.send(
157-
limit.message.format(cur_value=cur_value, threshold=limit.threshold)
158-
+ f" `@{self.local_machine_name}`"
159-
)
160-
self.bad_checker.mark_notified(name)
161-
self.stats["num_limits_notified"] += 1
162-
else:
163-
if self.bad_checker.decrease_counter(name, limit):
164-
# get one-time True if changed from non-normal to normal
165-
await self.send(
166-
f"*{limit.name} has recovered*" f" `@{self.local_machine_name}`"
167-
)
168-
self.stats["num_normal_notified"] += 1
169-
170-
@observe_system.before_loop
171-
async def before_observe_start(self):
172-
LOGGER.debug("Wait for observer bot to be ready ...")
173-
await self.bot.wait_until_ready()
174-
175-
async def send(self, message):
176-
# TODO: send to default channel?
177-
channel = self.bot.get_channel(self.channel_id)
178-
await channel.send(message)
179-
180-
def cog_unload(self):
181-
self.observe_system.cancel() # pylint: disable=no-member
182-
183-
@commands.command(name="observer-start")
184-
async def start(self, ctx):
185-
"""Starts the background system observer loop."""
186-
# NOTE: check for is_running() only added in version 1.4.0
187-
if self.observe_system.get_task() is None: # pylint: disable=no-member
188-
self.observe_system.start() # pylint: disable=no-member
189-
await ctx.send("Observer started")
190-
else:
191-
self.observe_system.restart() # pylint: disable=no-member
192-
await ctx.send("Observer restarted")
193-
194-
@commands.command(name="observer-stop")
195-
async def stop(self, ctx):
196-
"""Stops the background system observer."""
197-
self.observe_system.cancel() # pylint: disable=no-member
198-
self.reset_notifications()
199-
await ctx.send("Observer stopped")
200-
201-
@commands.command(name="observer-status")
202-
async def status(self, ctx):
203-
"""Displays statistics about notifications etc."""
204-
205-
if not self.stats:
206-
await ctx.send(f"N/A [`{self.local_machine_name}`] [`not-started`]")
207-
return
208-
209-
len_keys = max(len(k) for k in self.stats.keys())
210-
len_vals = max(
211-
len(str(v))
212-
for v in self.stats.values()
213-
if isinstance(v, (int, float, bool))
214-
)
215-
216-
try:
217-
# pylint: disable=no-member
218-
next_time = self.observe_system.next_iteration - datetime.datetime.now(
219-
datetime.timezone.utc
220-
)
221-
# pylint: enable=no-member
222-
except TypeError:
223-
# if stopped, then ``next_iteration`` is None
224-
next_time = "?"
225-
226-
message = "".join(
227-
[
228-
f"**Observer status for** `{self.local_machine_name}`",
229-
f""" [`{"running" if self.observe_system.next_iteration is not None else "stopped"}`]""", # pylint: disable=no-member
230-
"\n```\n",
231-
"\n".join(
232-
[f"{k:<{len_keys}} {v:>{len_vals}}" for k, v in self.stats.items()]
233-
),
234-
"\n```",
235-
f"\nNext check in `{next_time}`",
236-
]
237-
)
238-
239-
await ctx.send(message)
240-
241-
242-
def run_observer(token, channel_id):
243-
observer_bot = commands.Bot(command_prefix=".")
244-
245-
@observer_bot.event
246-
async def on_ready(): # pylint: disable=unused-variable
247-
LOGGER.info(f"Logged on as {observer_bot.user}")
248-
LOGGER.debug(f"name: {observer_bot.user.name}, id: {observer_bot.user.id}")
249-
250-
if channel_id is not None:
251-
channel = observer_bot.get_channel(channel_id)
252-
LOGGER.info(f"Channel: {channel} {type(channel)} {repr(channel)}")
253-
await channel.send(
254-
f"Running observer bot on `{get_local_machine_name()}`...\n"
255-
f"Type `{observer_bot.command_prefix}help` to display available commands."
256-
)
257-
258-
await observer_bot.change_presence(status=discord.Status.idle)
259-
260-
# TODO: maybe start observe_system task here (if required?)
261-
262-
@observer_bot.event
263-
async def on_disconnect(): # pylint: disable=unused-variable
264-
LOGGER.warning(f"Bot {observer_bot.user} disconnected!")
265-
266-
@observer_bot.command()
267-
async def ping(ctx): # pylint: disable=unused-variable
268-
"""Standard Ping-Pong latency/is-alive test."""
269-
await ctx.send(f"Pong (latency: {observer_bot.latency * 1000:.1f} ms)")
270-
271-
@observer_bot.command()
272-
async def info(ctx): # pylint: disable=unused-variable
273-
"""Query local system information and send it back."""
274-
# message = get_info_message()
275-
# await ctx.send(message)
276-
embed = make_sysinfo_embed()
277-
await ctx.send(embed=embed)
278-
279-
observer_bot.add_cog(SystemResourceObserverCog(observer_bot, channel_id))
280-
281-
LOGGER.info("Start observer bot ...")
282-
observer_bot.run(token)
283-
LOGGER.info("Quit observer bot.")
284-
285-
286-
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)