Add subreddit command

This commit is contained in:
Zeva Rose 2022-04-20 00:33:32 -06:00
parent 5e5aedebf6
commit 5123b6f198
4 changed files with 283 additions and 8 deletions

147
jarvis/cogs/reddit.py Normal file
View file

@ -0,0 +1,147 @@
"""JARVIS Reddit cog."""
import asyncio
import logging
from asyncpraw import Reddit
from asyncprawcore.exceptions import Forbidden, NotFound
from dis_snek import InteractionContext, Permissions, Scale, Snake
from dis_snek.client.utils.misc_utils import get
from dis_snek.models.discord.channel import ChannelTypes, GuildText
from dis_snek.models.discord.components import ActionRow, Select, SelectOption
from dis_snek.models.snek.application_commands import (
OptionTypes,
SlashCommand,
slash_option,
)
from dis_snek.models.snek.command import check
from jarvis_core.db import q
from jarvis_core.db.models import Subreddit, SubredditFollow
from jarvis import const
from jarvis.config import JarvisConfig
from jarvis.utils.permissions import admin_or_permissions
DEFAULT_USER_AGENT = f"python:JARVIS-Tasks:{const.__version__} (by u/zevaryx)"
class RedditCog(Scale):
"""JARVIS Reddit Cog."""
def __init__(self, bot: Snake):
self.bot = bot
self.logger = logging.getLogger(__name__)
config = JarvisConfig.from_yaml()
config.reddit["user_agent"] = config.reddit.get("user_agent", DEFAULT_USER_AGENT)
self.api = Reddit(**config.reddit)
reddit = SlashCommand(name="reddit", description="Manage Reddit follows")
@reddit.subcommand(sub_cmd_name="follow", sub_cmd_description="Follow a Subreddit")
@slash_option(
name="name",
description="Subreddit display name",
opt_type=OptionTypes.STRING,
required=True,
)
@slash_option(
name="channel",
description="Channel to post to",
opt_type=OptionTypes.CHANNEL,
channel_types=[ChannelTypes.GUILD_TEXT],
required=True,
)
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _reddit_follow(self, ctx: InteractionContext, name: str, channel: GuildText) -> None:
name = name.replace("r/", "")
if len(name) > 20:
await ctx.send("Invalid Subreddit name", ephemeral=True)
return
if not isinstance(channel, GuildText):
await ctx.send("Channel must be a text channel", ephemeral=True)
return
try:
subreddit = await self.api.subreddit(name)
except (NotFound, Forbidden) as e:
self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} on add")
await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True)
return
sr = await Subreddit.find_one(q(display_name=subreddit.display_name))
if not sr:
sr = Subreddit(display_name=subreddit.display_name, over18=subreddit.over18)
await sr.commit()
srf = SubredditFollow(
display_name=subreddit.display_name,
channel=channel.id,
guild=ctx.guild.id,
admin=ctx.author.id,
)
await srf.commit()
await ctx.send(f"Now following `r/{name}` in {channel.mention}")
@reddit.subcommand(sub_cmd_name="unfollow", sub_cmd_description="Unfollow Subreddits")
@check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _subreddit_unfollow(self, ctx: InteractionContext) -> None:
subs = SubredditFollow.find(q(guild=ctx.guild.id))
subreddits = []
async for sub in subs:
subreddits.append(sub)
if not subreddits:
await ctx.send("You need to follow a Subreddit first", ephemeral=True)
return
options = []
names = []
for idx, subreddit in enumerate(subreddits):
sub = await Subreddit.find_one(q(display_name=subreddit.display_name))
names.append(sub.display_name)
option = SelectOption(label=sub.display_name, value=str(idx))
options.append(option)
select = Select(
options=options, custom_id="to_delete", min_values=1, max_values=len(subreddits)
)
components = [ActionRow(select)]
block = "\n".join(x for x in names)
message = await ctx.send(
content=f"You are following the following subreddits:\n```\n{block}\n```\n\n"
"Please choose subreddits to unfollow",
components=components,
)
try:
context = await self.bot.wait_for_component(
check=lambda x: ctx.author.id == x.context.author.id,
messages=message,
timeout=60 * 5,
)
for to_delete in context.context.values:
follow = get(subreddits, guild=ctx.guild.id, display_name=names[int(to_delete)])
try:
await follow.delete()
except Exception:
self.logger.debug("Ignoring deletion error")
for row in components:
for component in row.components:
component.disabled = True
block = "\n".join(names[int(x)] for x in context.context.values)
await context.context.edit_origin(
content=f"Unfollowed the following:\n```\n{block}\n```", components=components
)
except asyncio.TimeoutError:
for row in components:
for component in row.components:
component.disabled = True
await message.edit(components=components)
def setup(bot: Snake) -> None:
"""Add RedditCog to JARVIS"""
if JarvisConfig.from_yaml().reddit:
RedditCog(bot)

View file

@ -21,6 +21,7 @@ class JarvisConfig(CConfig):
"gitlab_token": None, "gitlab_token": None,
"max_messages": 1000, "max_messages": 1000,
"twitter": None, "twitter": None,
"reddit": None,
} }

142
poetry.lock generated
View file

@ -12,6 +12,14 @@ caio = ">=0.9.0,<0.10.0"
[package.extras] [package.extras]
develop = ["aiomisc", "asynctest", "pytest", "pytest-cov"] develop = ["aiomisc", "asynctest", "pytest", "pytest-cov"]
[[package]]
name = "aiofiles"
version = "0.6.0"
description = "File support for asyncio."
category = "main"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
version = "3.8.1" version = "3.8.1"
@ -43,6 +51,25 @@ python-versions = ">=3.6"
[package.dependencies] [package.dependencies]
frozenlist = ">=1.1.0" frozenlist = ">=1.1.0"
[[package]]
name = "aiosqlite"
version = "0.17.0"
description = "asyncio bridge to the standard sqlite3 module"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
typing_extensions = ">=3.7.2"
[[package]]
name = "async-generator"
version = "1.10"
description = "Async generators and context managers for Python 3.5+"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]] [[package]]
name = "async-timeout" name = "async-timeout"
version = "4.0.2" version = "4.0.2"
@ -51,6 +78,61 @@ category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
[[package]]
name = "asyncio-extras"
version = "1.3.2"
description = "Asynchronous generators, context managers and more for asyncio"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
async-generator = ">=1.3"
[package.extras]
doc = ["sphinx-autodoc-typehints"]
test = ["pytest", "pytest-asyncio", "pytest-cov"]
[[package]]
name = "asyncpraw"
version = "7.5.0"
description = "Async PRAW, an abbreviation for `Asynchronous Python Reddit API Wrapper`, is a python package that allows for simple access to reddit's API."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
aiofiles = "<=0.6.0"
aiosqlite = "<=0.17.0"
asyncio-extras = "<=1.3.2"
asyncprawcore = ">=2.1,<3"
update-checker = ">=0.18"
[package.extras]
ci = ["coveralls"]
dev = ["packaging", "pre-commit", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-trio", "asynctest (>=0.13.0)", "mock (>=0.8)", "pytest (>=2.7.3)", "pytest-asyncio", "pytest-vcr", "testfixtures (>4.13.2,<7)", "vcrpy (==4.1.1)"]
lint = ["pre-commit", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-trio"]
readthedocs = ["sphinx", "sphinx-rtd-theme", "sphinxcontrib-trio"]
test = ["asynctest (>=0.13.0)", "mock (>=0.8)", "pytest (>=2.7.3)", "pytest-asyncio", "pytest-vcr", "testfixtures (>4.13.2,<7)", "vcrpy (==4.1.1)"]
[[package]]
name = "asyncprawcore"
version = "2.3.0"
description = "Low-level asynchronous communication layer for Async PRAW 7+."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
aiohttp = "*"
yarl = "*"
[package.extras]
ci = ["coveralls"]
dev = ["black", "flake8", "flynt", "pre-commit", "pydocstyle", "asynctest (>=0.13.0)", "mock (>=0.8)", "pytest", "pytest-vcr", "testfixtures (>4.13.2,<7)", "vcrpy (==4.0.2)"]
lint = ["black", "flake8", "flynt", "pre-commit", "pydocstyle"]
test = ["asynctest (>=0.13.0)", "mock (>=0.8)", "pytest", "pytest-vcr", "testfixtures (>4.13.2,<7)", "vcrpy (==4.0.2)"]
[[package]] [[package]]
name = "attrs" name = "attrs"
version = "21.4.0" version = "21.4.0"
@ -183,15 +265,15 @@ python-versions = ">=3.5"
[[package]] [[package]]
name = "jarvis-core" name = "jarvis-core"
version = "0.7.0" version = "0.8.0"
description = "" description = "JARVIS core"
category = "main" category = "main"
optional = false optional = false
python-versions = "^3.10" python-versions = "^3.10"
develop = false develop = false
[package.dependencies] [package.dependencies]
dis-snek = "*" aiohttp = "^3.8.1"
motor = "^2.5.1" motor = "^2.5.1"
orjson = "^3.6.6" orjson = "^3.6.6"
pytz = "^2022.1" pytz = "^2022.1"
@ -202,7 +284,7 @@ umongo = "^3.1.0"
type = "git" type = "git"
url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git"
reference = "main" reference = "main"
resolved_reference = "15521e73fb84fb4282a484ff0c9bb88ed1d144ae" resolved_reference = "02b60a29ebbab91d9ad4cf876eb10c2aea6f2231"
[[package]] [[package]]
name = "marshmallow" name = "marshmallow"
@ -333,11 +415,11 @@ python-versions = ">=3.10"
aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""}
aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""}
async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""}
attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""}
certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""}
charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""}
frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""}
idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""}
multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""}
pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""}
requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""}
@ -610,6 +692,22 @@ mongomock = ["mongomock"]
motor = ["motor (>=2.0,<3.0)"] motor = ["motor (>=2.0,<3.0)"]
txmongo = ["txmongo (>=19.2.0)"] txmongo = ["txmongo (>=19.2.0)"]
[[package]]
name = "update-checker"
version = "0.18.0"
description = "A python module that will check for package updates."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
requests = ">=2.3.0"
[package.extras]
dev = ["black", "flake8", "pytest (>=2.7.3)"]
lint = ["black", "flake8"]
test = ["pytest (>=2.7.3)"]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "1.26.8" version = "1.26.8"
@ -638,13 +736,17 @@ multidict = ">=4.0"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "ed0f4a3c6383dfad0b1aa4c2a45e14283972734400b9b29095a04dfb7d14eb7b" content-hash = "2397e36142e58e3a9a9215a6dae4e37c054fba46f168fb945ec26974bb9c2f21"
[metadata.files] [metadata.files]
aiofile = [ aiofile = [
{file = "aiofile-3.7.4-py3-none-any.whl", hash = "sha256:0e2a524e4714efda47ce8964b13d4da94cf553411f9f6da813df615a4cd73d95"}, {file = "aiofile-3.7.4-py3-none-any.whl", hash = "sha256:0e2a524e4714efda47ce8964b13d4da94cf553411f9f6da813df615a4cd73d95"},
{file = "aiofile-3.7.4.tar.gz", hash = "sha256:0aefa1d91d000d3a20a515d153db2ebf713076c7c94edf2fca85d3d83316abc5"}, {file = "aiofile-3.7.4.tar.gz", hash = "sha256:0aefa1d91d000d3a20a515d153db2ebf713076c7c94edf2fca85d3d83316abc5"},
] ]
aiofiles = [
{file = "aiofiles-0.6.0-py3-none-any.whl", hash = "sha256:bd3019af67f83b739f8e4053c6c0512a7f545b9a8d91aaeab55e6e0f9d123c27"},
{file = "aiofiles-0.6.0.tar.gz", hash = "sha256:e0281b157d3d5d59d803e3f4557dcc9a3dff28a4dd4829a9ff478adae50ca092"},
]
aiohttp = [ aiohttp = [
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"},
{file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"},
@ -723,10 +825,30 @@ aiosignal = [
{file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"},
{file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"},
] ]
aiosqlite = [
{file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"},
{file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"},
]
async-generator = [
{file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"},
{file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"},
]
async-timeout = [ async-timeout = [
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
] ]
asyncio-extras = [
{file = "asyncio_extras-1.3.2-py3-none-any.whl", hash = "sha256:839568ba07c3470c9aa2c441aa2417c108f7d3755862bc2bd39d69b524303993"},
{file = "asyncio_extras-1.3.2.tar.gz", hash = "sha256:084b62bebc19c6ba106d438a274bbb5566941c469128cd4af1a85f00a2c81f8d"},
]
asyncpraw = [
{file = "asyncpraw-7.5.0-py3-none-any.whl", hash = "sha256:b40f3db3464077a7a7e30a89181ba15ba4c5bc550dc2642e815b235f42ad8eb2"},
{file = "asyncpraw-7.5.0.tar.gz", hash = "sha256:61aabf05052472d8b29e0f0500a6ec8b483129374d36dad286d94e4b6864572d"},
]
asyncprawcore = [
{file = "asyncprawcore-2.3.0-py3-none-any.whl", hash = "sha256:46c52e6cfe91801a8c9490a0ee29a85cbc6713ccc535d5c704d448aee9729e5b"},
{file = "asyncprawcore-2.3.0.tar.gz", hash = "sha256:2a4a2d1ca7f78c8fa7d4903e6bd18cfe96742ad1f167b59473f64be0e7060d5d"},
]
attrs = [ attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
@ -1390,6 +1512,10 @@ umongo = [
{file = "umongo-3.1.0-py2.py3-none-any.whl", hash = "sha256:f6913027651ae673d71aaf54285f9ebf1e49a3f57662e526d029ba72e1a3fcd5"}, {file = "umongo-3.1.0-py2.py3-none-any.whl", hash = "sha256:f6913027651ae673d71aaf54285f9ebf1e49a3f57662e526d029ba72e1a3fcd5"},
{file = "umongo-3.1.0.tar.gz", hash = "sha256:20c72f09edae931285c22c1928862af35b90ec639a4dac2dbf015aaaac00e931"}, {file = "umongo-3.1.0.tar.gz", hash = "sha256:20c72f09edae931285c22c1928862af35b90ec639a4dac2dbf015aaaac00e931"},
] ]
update-checker = [
{file = "update_checker-0.18.0-py3-none-any.whl", hash = "sha256:cbba64760a36fe2640d80d85306e8fe82b6816659190993b7bdabadee4d4bbfd"},
{file = "update_checker-0.18.0.tar.gz", hash = "sha256:6a2d45bb4ac585884a6b03f9eade9161cedd9e8111545141e9aa9058932acb13"},
]
urllib3 = [ urllib3 = [
{file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},

View file

@ -23,6 +23,7 @@ pastypy = "^1.0.1"
dateparser = "^1.1.1" dateparser = "^1.1.1"
aiofile = "^3.7.4" aiofile = "^3.7.4"
molter = "^0.11.0" molter = "^0.11.0"
asyncpraw = "^7.5.0"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]