aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Pipfile.lock68
-rw-r--r--bot/constants.py27
-rw-r--r--bot/exts/moderation/stream.py106
-rw-r--r--tests/bot/exts/moderation/test_stream.py56
4 files changed, 72 insertions, 185 deletions
diff --git a/Pipfile.lock b/Pipfile.lock
index 541db1627..25fcab4b1 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -187,6 +187,7 @@
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
],
+ "index": "pypi",
"markers": "sys_platform == 'win32'",
"version": "==0.4.4"
},
@@ -231,10 +232,10 @@
},
"fakeredis": {
"hashes": [
- "sha256:8070b7fce16f828beaef2c757a4354af91698685d5232404f1aeeb233529c7a5",
- "sha256:f8c8ea764d7b6fd801e7f5486e3edd32ca991d506186f1923a01fc072e33c271"
+ "sha256:01cb47d2286825a171fb49c0e445b1fa9307087e07cbb3d027ea10dbff108b6a",
+ "sha256:2c6041cf0225889bc403f3949838b2c53470a95a9e2d4272422937786f5f8f73"
],
- "version": "==1.4.4"
+ "version": "==1.4.5"
},
"feedparser": {
"hashes": [
@@ -538,6 +539,15 @@
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
+ "pyreadline": {
+ "hashes": [
+ "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1",
+ "sha256:65540c21bfe14405a3a77e4c085ecfce88724743a4ead47c66b84defcf82c32e",
+ "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b"
+ ],
+ "markers": "sys_platform == 'win32'",
+ "version": "==2.1"
+ },
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
@@ -555,18 +565,18 @@
},
"pyyaml": {
"hashes": [
- "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
- "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
- "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
+ "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
+ "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
- "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
- "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
+ "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
- "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
- "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
+ "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
+ "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
+ "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
+ "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
"index": "pypi",
@@ -846,11 +856,11 @@
},
"flake8-bugbear": {
"hashes": [
- "sha256:a3ddc03ec28ba2296fc6f89444d1c946a6b76460f859795b35b77d4920a51b63",
- "sha256:bd02e4b009fb153fe6072c31c52aeab5b133d508095befb2ffcf3b41c4823162"
+ "sha256:528020129fea2dea33a466b9d64ab650aa3e5f9ffc788b70ea4bc6cf18283538",
+ "sha256:f35b8135ece7a014bc0aee5b5d485334ac30a6da48494998cc1fabf7ec70d703"
],
"index": "pypi",
- "version": "==20.1.4"
+ "version": "==20.11.1"
},
"flake8-docstrings": {
"hashes": [
@@ -900,11 +910,11 @@
},
"identify": {
"hashes": [
- "sha256:5dd84ac64a9a115b8e0b27d1756b244b882ad264c3c423f42af8235a6e71ca12",
- "sha256:c9504ba6a043ee2db0a9d69e43246bc138034895f6338d5aed1b41e4a73b1513"
+ "sha256:943cd299ac7f5715fcb3f684e2fc1594c1e0f22a90d15398e5888143bd4144b5",
+ "sha256:cc86e6a9a390879dcc2976cef169dd9cc48843ed70b7380f321d1b118163c60e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==1.5.9"
+ "version": "==1.5.10"
},
"idna": {
"hashes": [
@@ -938,11 +948,11 @@
},
"pre-commit": {
"hashes": [
- "sha256:22e6aa3bd571debb01eb7d34483f11c01b65237be4eebbf30c3d4fb65762d315",
- "sha256:905ebc9b534b991baec87e934431f2d0606ba27f2b90f7f652985f5a5b8b6ae6"
+ "sha256:4aee0db4808fa48d2458cedd5b9a084ef24dda1a0fa504432a11977a4d1cfd0a",
+ "sha256:b2d106d51c6ba6217e859d81774aae33fd825fe7de0dcf0c46e2586333d7a92e"
],
"index": "pypi",
- "version": "==2.8.2"
+ "version": "==2.9.0"
},
"pycodestyle": {
"hashes": [
@@ -970,18 +980,18 @@
},
"pyyaml": {
"hashes": [
- "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
- "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
- "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
+ "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
+ "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
- "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
- "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
+ "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
- "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
- "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
+ "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
+ "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
+ "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
+ "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
"index": "pypi",
@@ -1028,11 +1038,11 @@
},
"virtualenv": {
"hashes": [
- "sha256:b0011228208944ce71052987437d3843e05690b2f23d1c7da4263fde104c97a2",
- "sha256:b8d6110f493af256a40d65e29846c69340a947669eec8ce784fcf3dd3af28380"
+ "sha256:07cff122e9d343140366055f31be4dcd61fd598c69d11cd33a9d9c8df4546dd7",
+ "sha256:e0aac7525e880a429764cefd3aaaff54afb5d9f25c82627563603f5d7de5a6e5"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==20.1.0"
+ "version": "==20.2.1"
}
}
}
diff --git a/bot/constants.py b/bot/constants.py
index 33ed29c39..dca83e7ab 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -705,30 +705,3 @@ ERROR_REPLIES = [
"Noooooo!!",
"I can't believe you've done this",
]
-
-# TIME_FORMATS defines aliases and multipliers for time formats
-# key is a standard time unit name like second ,year, decade etc.
-# mul is a multiplier where duration of said time unit * multiplier = time in seconds
-# eg. 1 day = 1 * multiplier seconds, so mul = 86400
-TIME_FORMATS = {
- "second": {
- "aliases": ("s", "sec", "seconds", "secs"),
- "mul": 1
- },
- "minute": {
- "aliases": ("m", "min", "mins", "minutes"),
- "mul": 60
- },
- "hour": {
- "aliases": ("h", "hr", "hrs", "hours"),
- "mul": 3600
- },
- "day": {
- "aliases": ("d", "days"),
- "mul": 86400
- },
- "year": {
- "aliases": ("yr", "yrs", "years", "y"),
- "mul": 31536000
- }
-}
diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py
index 7dd72a95b..0fc004d75 100644
--- a/bot/exts/moderation/stream.py
+++ b/bot/exts/moderation/stream.py
@@ -1,11 +1,11 @@
-import time
-
import discord
-from async_rediscache import RedisCache
-from discord.ext import commands, tasks
+from discord.ext import commands
from bot.bot import Bot
-from bot.constants import Guild, Roles, STAFF_ROLES, TIME_FORMATS, Emojis
+from bot.constants import Emojis, Roles, STAFF_ROLES
+from bot.converters import Expiry
+from bot.utils.scheduling import Scheduler
+from bot.utils.time import format_infraction_with_duration
# Constant error messages
TIME_FORMAT_NOT_VALID = f"{Emojis.cross_mark}Please specify a valid time format ex. 10h or 1day."
@@ -14,37 +14,17 @@ USER_ALREADY_ALLOWED_TO_STREAM = f"{Emojis.cross_mark}This user can already stre
USER_ALREADY_NOT_ALLOWED_TO_STREAM = f"{Emojis.cross_mark}This user already can't stream."
-# FORMATS holds a combined list of all allowed time units
-# made from TIME_FORMATS constant
-FORMATS = []
-for key, entry in TIME_FORMATS.items():
- FORMATS.extend(entry["aliases"])
- FORMATS.append(key)
-
-
class Stream(commands.Cog):
"""Grant and revoke streaming permissions from users."""
- # Data cache storing userid to unix_time relation
- # user id is used to get member who's streaming permission need to be revoked after some time
- # unix_time is a time when user's streaming permission needs tp be revoked in unix time notation
- user_cache = RedisCache()
-
def __init__(self, bot: Bot):
self.bot = bot
- self.remove_permissions.start()
- self.guild_static = None
+ self.scheduler = Scheduler(self.__class__.__name__)
@staticmethod
- def _link_from_alias(time_format: str) -> (dict, str):
- """Get TIME_FORMATS key and entry by time format or any of its aliases."""
- for format_key, val in TIME_FORMATS.items():
- if format_key == time_format or time_format in val["aliases"]:
- return TIME_FORMATS[format_key], format_key
-
- def _parse_time_to_seconds(self, duration: int, time_format: str) -> int:
- """Get time in seconds from duration and time format."""
- return duration * self._link_from_alias(time_format)[0]["mul"]
+ async def _remove_streaming_permission(schedule_user: discord.Member) -> None:
+ """Remove streaming permission from Member"""
+ await schedule_user.remove_roles(discord.Object(Roles.video), reason="Temporary streaming access revoked")
@commands.command(aliases=("streaming",))
@commands.has_any_role(*STAFF_ROLES)
@@ -52,68 +32,48 @@ class Stream(commands.Cog):
self,
ctx: commands.Context,
user: discord.Member,
- duration: int = 1,
- time_format: str = "h",
+ duration: Expiry,
*_
) -> None:
"""
- Stream handles <prefix>stream command.
-
- argument user - required user mention, any errors should be handled by upper level handler
- duration - int must be higher than 0 - defaults to 1
- time_format - str defining what time unit you want to use, must be any of FORMATS - defaults to h
- Command give user permission to stream and takes it away after provided duration
+ Temporarily grant streaming permissions to a user for a given duration.
+ A unit of time should be appended to the duration.
+ Units (∗case-sensitive):
+ \u2003`y` - years
+ \u2003`m` - months∗
+ \u2003`w` - weeks
+ \u2003`d` - days
+ \u2003`h` - hours
+ \u2003`M` - minutes∗
+ \u2003`s` - seconds
+ Alternatively, an ISO 8601 timestamp can be provided for the duration.
"""
- # Time can't be negative lol
- if duration <= 0:
- await ctx.send(TIME_LESS_EQ_0)
- return
-
- # Check if time_format argument is a valid time format
- # eg. d, day etc are aliases for day time format
- if time_format not in FORMATS:
- await ctx.send(TIME_FORMAT_NOT_VALID)
- return
-
# Check if user already has streaming permission
already_allowed = any(Roles.video == role.id for role in user.roles)
if already_allowed:
await ctx.send(USER_ALREADY_ALLOWED_TO_STREAM)
return
- # Set user id - time in redis cache and add streaming permission role
- await self.user_cache.set(user.id, time.time() + self._parse_time_to_seconds(duration, time_format))
+ # Schedule task to remove streaming permission from Member
+ self.scheduler.schedule_at(duration, user.id, self._remove_streaming_permission(user))
await user.add_roles(discord.Object(Roles.video), reason="Temporary streaming access granted")
- await ctx.send(f"{Emojis.check_mark}{user.mention} can now stream for "
- f"{duration} {self._link_from_alias(time_format)[1]}/s.")
-
- @tasks.loop(seconds=30)
- async def remove_permissions(self) -> None:
- """Background loop for removing streaming permission."""
- all_entries = await self.user_cache.items()
- for user_id, delete_time in all_entries:
- if time.time() > delete_time:
- member = self.guild_static.fetch_memeber(user_id)
- if member:
- await member.remove_roles(discord.Object(Roles.video), reason="Temporary streaming access revoked")
- await self.user_cache.pop(user_id)
-
- @remove_permissions.before_loop
- async def await_ready(self) -> None:
- """Wait for bot to be ready before starting remove_permissions loop and get guild by id."""
- await self.bot.wait_until_ready()
- self.guild_static = self.bot.get_guild(Guild.id)
+ await ctx.send(f"{Emojis.check_mark}{user.mention} can now stream until "
+ f"{format_infraction_with_duration(str(duration))}.")
@commands.command(aliases=("unstream", ))
@commands.has_any_role(*STAFF_ROLES)
async def revokestream(
self,
ctx: commands.Context,
- user: discord.Member = None
+ user: discord.Member
) -> None:
- """Revoke streaming permissions from a user."""
- not_allowed = not any(Roles.video == role.id for role in user.roles)
- if not_allowed:
+ """Take away streaming permission from a user"""
+ # Check if user has the streaming permission to begin with
+ allowed = any(Roles.video == role.id for role in user.roles)
+ if allowed:
+ # Cancel scheduled task to take away streaming permission to avoid errors
+ if user.id in self.scheduler:
+ self.scheduler.cancel(user.id)
await user.remove_roles(discord.Object(Roles.video))
await ctx.send(f"{Emojis.check_mark}Streaming permission taken from {user.display_name}")
else:
diff --git a/tests/bot/exts/moderation/test_stream.py b/tests/bot/exts/moderation/test_stream.py
index 467c373aa..15956a9de 100644
--- a/tests/bot/exts/moderation/test_stream.py
+++ b/tests/bot/exts/moderation/test_stream.py
@@ -1,28 +1,10 @@
-import asyncio
import unittest
-from async_rediscache import RedisSession
from bot.constants import Roles
from bot.exts.moderation.stream import Stream
from tests.helpers import MockBot, MockMember, MockRole
-redis_session = None
-redis_loop = asyncio.get_event_loop()
-
-
-def setUpModule(): # noqa: N802
- """Create and connect to the fakeredis session."""
- global redis_session
- redis_session = RedisSession(use_fakeredis=True)
- redis_loop.run_until_complete(redis_session.connect())
-
-
-def tearDownModule(): # noqa: N802
- """Close the fakeredis session."""
- if redis_session:
- redis_loop.run_until_complete(redis_session.close())
-
class StreamCommandTest(unittest.IsolatedAsyncioTestCase):
@@ -30,44 +12,6 @@ class StreamCommandTest(unittest.IsolatedAsyncioTestCase):
self.bot = MockBot()
self.cog = Stream(self.bot)
- def test_linking_time_format_from_alias_or_key(self):
- """
- User provided time format needs to be lined to a proper entry in TIME_FORMATS
- This Test checks _link_from_alias method
- Checking for whether alias or key exists in TIME_FORMATS is done before calling this function
- """
-
- test_cases = (("sec", "second"),
- ("s", "second"),
- ("seconds", "second"),
- ("second", "second"),
- ("secs", "second"),
- ("min", "minute"),
- ("m", "minute"),
- ("minutes", "minute"),
- ("hr", "hour"),
- ("hrs", "hour"),
- ("hours", "hour"),
- ("d", "day"),
- ("days", "day"),
- ("yr", "year"),
- ("yrs", "year"),
- ("y", "year"))
-
- for case in test_cases:
- linked = self.cog._link_from_alias(case[0])[1]
- self.assertEqual(linked, case[1])
-
- def test_parsing_duration_and_time_format_to_seconds(self):
- """
- Test calculating time in seconds from duration and time unit
- This test is technically dependent on _link_from_alias function, not the best practice but necessary
- """
- test_cases = ((1, "minute", 60), (5, "second", 5), (2, "day", 172800))
- for case in test_cases:
- time_in_seconds = self.cog._parse_time_to_seconds(case[0], case[1])
- self.assertEqual(time_in_seconds, case[2])
-
def test_checking_if_user_has_streaming_permission(self):
"""
Test searching for video role in Member.roles