Added rate limiting to notifications to limit them to once per 20 minutes per stream.
There's also a small amount of preliminary work included in this commit for a new type of notification when streams change their title in the middle of a session.
This commit is contained in:
@@ -8,6 +8,7 @@ import sqlite3
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import time
|
||||||
|
|
||||||
from maubot import Plugin, MessageEvent
|
from maubot import Plugin, MessageEvent
|
||||||
from maubot.handlers import command
|
from maubot.handlers import command
|
||||||
@@ -28,6 +29,16 @@ USER_AGENT = (
|
|||||||
"OwncastSentry/1.0.3 (bot; +https://git.logal.dev/LogalDeveloper/OwncastSentry)"
|
"OwncastSentry/1.0.3 (bot; +https://git.logal.dev/LogalDeveloper/OwncastSentry)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Hard minimum amount of time between when notifications can be sent for a stream. Prevents spamming notifications for glitchy or malicious streams.
|
||||||
|
SECONDS_BETWEEN_NOTIFICATIONS = 20 * 60 # 20 minutes in seconds
|
||||||
|
|
||||||
|
# I'm not sure the best way to name or explain this variable, so let's just say what uses it:
|
||||||
|
#
|
||||||
|
# After a stream goes offline, a timer is started. Then, ...
|
||||||
|
# - If a stream comes back online with the same title within this time, no notification is sent.
|
||||||
|
# - If a stream comes back online with a different title, a rename notification is sent.
|
||||||
|
# - If this time period passes entirely and a stream comes back online after, it's treated as regular going live.
|
||||||
|
TEMPORARY_OFFLINE_NOTIFICATION_COOLDOWN = 7 * 60 # 7 minutes in seconds
|
||||||
|
|
||||||
# ===== DATABASE MIGRATIONS =====
|
# ===== DATABASE MIGRATIONS =====
|
||||||
upgrade_table = UpgradeTable()
|
upgrade_table = UpgradeTable()
|
||||||
@@ -81,6 +92,12 @@ class OwncastSentry(Plugin):
|
|||||||
headers=headers, cookie_jar=cookie_jar, timeout=timeouts, connector=baseconnctor
|
headers=headers, cookie_jar=cookie_jar, timeout=timeouts, connector=baseconnctor
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Keeps track of when a notification was last sent for streams.
|
||||||
|
notification_timers_cache = {}
|
||||||
|
|
||||||
|
# Keeps track of when streams last went offline.
|
||||||
|
offline_timer_cache = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_db_upgrade_table(cls) -> UpgradeTable | None:
|
def get_db_upgrade_table(cls) -> UpgradeTable | None:
|
||||||
"""
|
"""
|
||||||
@@ -285,15 +302,13 @@ class OwncastSentry(Plugin):
|
|||||||
):
|
):
|
||||||
# Yes! This stream is now live. Send notifications and log it, if allowed.
|
# Yes! This stream is now live. Send notifications and log it, if allowed.
|
||||||
if send_notifications:
|
if send_notifications:
|
||||||
self.log.info(
|
self.log.info(f"[{domain}] Stream is now live!")
|
||||||
f"[{domain}] Stream is now live! Notifying subscribed rooms..."
|
|
||||||
)
|
|
||||||
await self.notify_rooms_of_stream_online(
|
await self.notify_rooms_of_stream_online(
|
||||||
domain, new_state["streamTitle"]
|
domain, new_state["streamTitle"], False
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.log.info(
|
self.log.info(
|
||||||
f"[{domain}] Stream is live, but performed first update. WIll not notify subscribed rooms."
|
f"[{domain}] Stream is live, but performed first update. Will not notify subscribed rooms."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Does the latest stream state no longer have a last connect time but the old state does?
|
# Does the latest stream state no longer have a last connect time but the old state does?
|
||||||
@@ -322,14 +337,32 @@ class OwncastSentry(Plugin):
|
|||||||
# All done.
|
# All done.
|
||||||
self.log.debug(f"[{domain}] State update completed.")
|
self.log.debug(f"[{domain}] State update completed.")
|
||||||
|
|
||||||
async def notify_rooms_of_stream_online(self, domain: str, title: str) -> None:
|
async def notify_rooms_of_stream_online(
|
||||||
|
self, domain: str, title: str, renamed: bool
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Sends notifications to rooms with subscriptions to the provided stream domain.
|
Sends notifications to rooms with subscriptions to the provided stream domain.
|
||||||
|
|
||||||
:param domain: The domain of the stream to send notifications for.
|
:param domain: The domain of the stream to send notifications for.
|
||||||
:param title: The title of the stream to include in the message.
|
:param title: The title of the stream to include in the message.
|
||||||
|
:param renamed: Whether or not this is for a stream changing its title rather than going live.
|
||||||
:return: Nothing.
|
:return: Nothing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Has enough time passed since the last notification was sent?
|
||||||
|
if domain in self.notification_timers_cache:
|
||||||
|
seconds_since_last_notification = round(
|
||||||
|
time.time() - self.notification_timers_cache[domain]
|
||||||
|
)
|
||||||
|
if seconds_since_last_notification < SECONDS_BETWEEN_NOTIFICATIONS:
|
||||||
|
self.log.info(
|
||||||
|
f"[{domain}] Not sending notifications. Only {seconds_since_last_notification} of required {SECONDS_BETWEEN_NOTIFICATIONS} seconds has passed since last notification."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Yes. Log the current time and proceed with sending notifications.
|
||||||
|
self.notification_timers_cache[domain] = time.time()
|
||||||
|
|
||||||
# Get a list of room IDs with active subscriptions to the stream domain.
|
# Get a list of room IDs with active subscriptions to the stream domain.
|
||||||
query = "SELECT room_id FROM subscriptions WHERE stream_domain=$1"
|
query = "SELECT room_id FROM subscriptions WHERE stream_domain=$1"
|
||||||
async with self.database.acquire() as connection:
|
async with self.database.acquire() as connection:
|
||||||
|
|||||||
Reference in New Issue
Block a user